#include <stdio.h>
#include "csapp.h"

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define MAX_CACHE_BLOCK (MAX_CACHE_SIZE / MAX_OBJECT_SIZE)

/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";
static const char *connection_hdr = "Connection: close\r\n";
static const char *proxy_connection_hdr = "Proxy-Connection: close\r\n";

typedef struct {
    char content[MAX_OBJECT_SIZE];
    char uri[MAXLINE];
    int size;
    int valid;
    time_t timestamp;
} CacheBlock;

typedef struct {
    CacheBlock blocks[MAX_CACHE_BLOCK];
    sem_t data_mutex, reader_mutex; // data_mutex: 读写互斥, reader_mutex: 读者之间互斥
    int reader_cnt;
} Cache;

Cache cache;

void init_cache();
int read_cache(char *uri, char *content);
void write_cache(char *uri, char *content, int size);
void proxy(int client_fd);
void parse_uri(char *uri, char **hostname, char **port, char **path);
void build_proxy_request(rio_t *client_rio, char *proxy_request, char *hostname, char *port, char *path);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
void *thread(void *vargp);

int main(int argc, char **argv)
{
    int listenfd, *connfd_ptr;
    pthread_t tid;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;
    char client_hostname[MAXLINE], client_port[MAXLINE];

    if (argc != 2) {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(0);
    }

    listenfd = Open_listenfd(argv[1]);

    init_cache();

    while (1) {
        clientlen = sizeof(struct sockaddr_storage);
        connfd_ptr = Malloc(sizeof(int));
        *connfd_ptr = Accept(listenfd, (SA *)&clientaddr, &clientlen);
        Getnameinfo((SA *)&clientaddr, clientlen, client_hostname, MAXLINE, client_port, MAXLINE, 0);
        printf("Connected to (%s, %s)\n", client_hostname, client_port);
        Pthread_create(&tid, NULL, thread, connfd_ptr);
    }

    return 0;
}

void init_cache() {
    Sem_init(&cache.data_mutex, 0, 1);
    Sem_init(&cache.reader_mutex, 0, 1);
    memset(&(cache.blocks), 0, sizeof(cache.blocks));
}

int read_cache(char *uri, char *content) {
    P(&cache.reader_mutex);
    cache.reader_cnt++;
    if (cache.reader_cnt == 1) P(&cache.data_mutex);
    V(&cache.reader_mutex);

    // 核心内容
    int i;
    for (i = 0; i < MAX_CACHE_BLOCK; i++) {
        if (cache.blocks[i].valid && strcmp(cache.blocks[i].uri, uri) == 0) {
            memcpy(content, cache.blocks[i].content, cache.blocks[i].size);
            break;
        }
    }

    P(&cache.reader_mutex);
    cache.reader_cnt--;
    if (cache.reader_cnt == 0) V(&cache.data_mutex);
    V(&cache.reader_mutex);
    
    return i < MAX_CACHE_BLOCK;
}

void write_cache(char *uri, char *content, int size) {
    // 大于最大对象大小不写入Cache
    if (size > MAX_OBJECT_SIZE) return;

    P(&cache.data_mutex);
    
    // 核心内容
    int max_timestamp = 0;
    int index = -1;
    for (int i = 0; i < MAX_CACHE_BLOCK; i++) {
        if (!cache.blocks[i].valid) {
            index = i;
            break;
        }
        // LRU
        if (cache.blocks[i].timestamp < max_timestamp) {
            max_timestamp = cache.blocks[i].timestamp;
            index = i;
        }
    }
    cache.blocks[index].size = size;
    cache.blocks[index].valid = 1;
    cache.blocks[index].timestamp = time(NULL);
    strcpy(cache.blocks[index].uri, uri);
    memcpy(cache.blocks[index].content, content, size);

    V(&cache.data_mutex);
}

void proxy(int client_fd) {
    rio_t client_rio, server_rio;
    char header[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];

    // 读取请求头
    Rio_readinitb(&client_rio, client_fd);
    if (Rio_readlineb(&client_rio, header, MAXLINE) <= 0) return;
    sscanf(header, "%s %s %s", method, uri, version);

    if (strcasecmp(method, "GET")) {
        clienterror(client_fd, method, "501", "Not Implemented", "Proxy does not implement this method");
        return;
    }

    // 读取Cache
    char cache_content[MAX_OBJECT_SIZE];
    if (read_cache(uri, cache_content)) {
        Rio_writen(client_fd, cache_content, strlen(cache_content));
        return;
    }

    // 未命中Cache, 连接服务器
    // 解析 uri
    char uri_copy[MAXLINE];
    strcpy(uri_copy, uri);
    char *hostname, *port, *path;
    parse_uri(uri, &hostname, &port, &path);
    if (!port) port = "80";
    if (!path) path = "";
    printf("hostname: %s, port: %s, path: %s\n", (hostname), (port), path);

    // 连接服务器
    int server_fd = Open_clientfd(hostname, port);
    if (server_fd < 0) {
        clienterror(client_fd, hostname, "404", "Not Found", "Proxy couldn't find this server");
        return;
    }
    Rio_readinitb(&server_rio, server_fd);

    // 创建代理请求并发送
    char proxy_request[MAXLINE];
    build_proxy_request(&client_rio, proxy_request, hostname, port, path);
    Rio_writen(server_fd, proxy_request, strlen(proxy_request));

    // 读取服务器响应并转发
    int buf_len;
    int content_size = 0;
    while ((buf_len = Rio_readlineb(&server_rio, cache_content + content_size, MAXLINE))) {
        Rio_writen(client_fd, cache_content + content_size, buf_len);
        content_size += buf_len;
    }

    // 写入Cache
    write_cache(uri_copy, cache_content, content_size);
}

// 解析uri, 获取hostname, port, path, 通过指针返回, NULL表示没有
void parse_uri(char *uri, char **hostname, char **port, char **path) {
    puts(uri);
    char *find_ptr = strstr(uri, "//");
    if (find_ptr) {
        // http://hostname...
        find_ptr += 2;
    } else {
        // hostname...
        find_ptr = uri;
    }
    *hostname = find_ptr;

    char *port_ptr = strstr(find_ptr, ":");
    if (port_ptr) {
        // hostname:port...
        *port = port_ptr + 1;
        *port_ptr = '\0';
        find_ptr = *port;
    } else {
        // no port
        *port = NULL;
    }

    char *path_ptr = strstr(find_ptr, "/");
    if (path_ptr) {
        // .../path...
        *path = path_ptr + 1;
        *path_ptr = '\0';
    } else {
        // no path
        *path = NULL;
    }
}

// 构建代理请求
void build_proxy_request(rio_t *client_rio, char *proxy_request, char *hostname, char *port, char *path) {
    char tmp[MAXLINE];
    sprintf(proxy_request, "GET /%s HTTP/1.0\r\n", path);
    sprintf(proxy_request + strlen(proxy_request), "Host: %s:%s\r\n", hostname, port);
    sprintf(proxy_request + strlen(proxy_request), "%s%s%s", user_agent_hdr, connection_hdr, proxy_connection_hdr);
    
    while (Rio_readlineb(client_rio, tmp, MAXLINE) > 0) {
        if (strstr(tmp, "\r\n")) break;
        if (strstr(tmp, "Host")) continue;
        if (strstr(tmp, "User-Agent")) continue;
        if (strstr(tmp, "Connection")) continue;
        if (strstr(tmp, "Proxy-Connection")) continue;
        sprintf(proxy_request + strlen(proxy_request), "%s", tmp);
    }
    sprintf(proxy_request + strlen(proxy_request), "\r\n");
}

/*
 * clienterror - returns an error message to the client
 */
/* $begin clienterror */
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
    char buf[MAXLINE];

    /* Print the HTTP response headers */
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n\r\n");
    Rio_writen(fd, buf, strlen(buf));

    /* Print the HTTP response body */
    sprintf(buf, "<html><title>Tiny Error</title>");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<body bgcolor=""ffffff"">\r\n");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "%s: %s\r\n", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<p>%s: %s\r\n", longmsg, cause);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<hr><em>The Tiny Web server</em>\r\n");
    Rio_writen(fd, buf, strlen(buf));
}
/* $end clienterror */

void *thread(void *vargp) {
    int connfd = *((int *)vargp);
    Pthread_detach(Pthread_self());
    Free(vargp);
    proxy(connfd);
    Close(connfd);
    return NULL;
}
